﻿using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Security.AccessControl;
using System.Security.Principal;

namespace Pst_Backup_Agent
{
    internal class SIDHelper
    {
        const int NO_ERROR = 0;
        const int ERROR_INSUFFICIENT_BUFFER = 122;
        const int ERROR_INVALID_FLAGS = 1004;

        enum SID_NAME_USE
        {
            SidTypeUser = 1,
            SidTypeGroup,
            SidTypeDomain,
            SidTypeAlias,
            SidTypeWellKnownGroup,
            SidTypeDeletedAccount,
            SidTypeInvalid,
            SidTypeUnknown,
            SidTypeComputer
        }

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool LookupAccountName(
             string lpSystemName,
             string lpAccountName,
             [MarshalAs(UnmanagedType.LPArray)] byte[] Sid,
             ref uint cbSid,
             StringBuilder ReferencedDomainName,
             ref uint cchReferencedDomainName,
             out SID_NAME_USE peUse);

        [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool ConvertSidToStringSid(
            [MarshalAs(UnmanagedType.LPArray)] byte[] pSID,
            out IntPtr ptrSid);

        [DllImport("kernel32.dll")]
        private static extern IntPtr LocalFree(IntPtr hMem);

        internal static void AddReadWriteAccess(DirectorySecurity folderACL, string accountToAdd)
        {
            try
            {
                AddAccessRule(folderACL, GetReadWriteAccessRule(GetSID(accountToAdd)));
            }
            catch (Exception) { }
        }

        internal static void AddFullControlAccess(DirectorySecurity folderACL, string accountToAdd)
        {
            try
            {
                AddAccessRule(folderACL, GetFullControlAccessRule(GetSID(accountToAdd)));
            }
            catch (Exception) { }
        }

        internal static FileSystemAccessRule GetReadWriteAccessRule(SecurityIdentifier SID)
        {
            return new FileSystemAccessRule(SID,
                    FileSystemRights.Traverse |
                    FileSystemRights.ListDirectory |
                    FileSystemRights.ReadAttributes |
                    FileSystemRights.ReadExtendedAttributes |
                    FileSystemRights.CreateDirectories |
                    FileSystemRights.CreateFiles |
                    FileSystemRights.WriteAttributes |
                    FileSystemRights.WriteExtendedAttributes |
                    FileSystemRights.Delete |
                    FileSystemRights.ReadPermissions,
                    InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
                    PropagationFlags.None,
                    AccessControlType.Allow);
        }

        internal static FileSystemAccessRule GetFullControlAccessRule(SecurityIdentifier SID)
        {
            return new FileSystemAccessRule(SID,
                FileSystemRights.FullControl,
                    InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
                    PropagationFlags.None,
                    AccessControlType.Allow);
        }

        internal static void AddAccessRule(DirectorySecurity folder, FileSystemAccessRule rule)
        {
            try
            {
                folder.AddAccessRule(rule);
            }
            catch (Exception) { }
        }

        private static SecurityIdentifier GetSID(string accountName)
        {
            try
            {
                return TranslateAccountNameIntoSID(null, accountName);
            }
            catch (Exception) { }

            string systemName = null;

            if (accountName.Contains("\\"))
            {
                int index = accountName.IndexOf('\\');
                systemName = accountName.Substring(0, index);
                accountName = accountName.Substring(index + 1);
            }
            else if (accountName.Contains("@"))
            {
                int index = accountName.IndexOf('@');
                systemName = accountName.Substring(index + 1);
                accountName = accountName.Substring(0, index);
            }

            return TranslateAccountNameIntoSID(systemName, accountName);
        }

        private static SecurityIdentifier TranslateAccountNameIntoSID(string systemName, string accountName)
        {
            if (accountName == null)
                throw new ArgumentNullException();

            string sidString = String.Empty;
            byte[] Sid = null;
            uint cbSid = 0;
            StringBuilder referencedDomainName = new StringBuilder();
            uint cchReferencedDomainName = (uint)referencedDomainName.Capacity;
            SID_NAME_USE sidUse;

            int err = NO_ERROR;
            if (!LookupAccountName(systemName, accountName, Sid, ref cbSid, referencedDomainName, ref cchReferencedDomainName, out sidUse))
            {
                err = Marshal.GetLastWin32Error();
                if (err == ERROR_INSUFFICIENT_BUFFER || err == ERROR_INVALID_FLAGS)
                {
                    Sid = new byte[cbSid];
                    referencedDomainName.EnsureCapacity((int)cchReferencedDomainName);
                    err = NO_ERROR;
                    if (!LookupAccountName(systemName, accountName, Sid, ref cbSid, referencedDomainName, ref cchReferencedDomainName, out sidUse))
                        err = Marshal.GetLastWin32Error();
                }
            }
            else
            {
                throw new Exception("Unable to translate " + accountName + " into a SID.");
            }
            if (err == 0)
            {
                IntPtr ptrSid;
                if (!ConvertSidToStringSid(Sid, out ptrSid))
                {
                    err = Marshal.GetLastWin32Error();
                    throw new Exception("Could not convert sid to string. Error : " + err.ToString());
                }
                else
                {
                    sidString = Marshal.PtrToStringAuto(ptrSid);
                    LocalFree(ptrSid);
                }
            }
            else
                throw new Exception("Error : " + err.ToString());

            return new SecurityIdentifier(sidString);
        }        
    }
}
